﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Forums.OfflineStore;
using System.Reflection;

namespace Microsoft.Forums.ForumsClient.Models
{
    /// <summary>
    /// A registry for forum related objects
    /// This create a single location for the in memory objects so that an update to them
    /// affects all displayed instanced of them
    /// </summary>
    public static class ObjectRegistry
    {
        #region Static Members

        private static List<Forum> forumObjects = new List<Forum>();
        private static Dictionary<Guid, Dictionary<Guid, Thread>> threadObjects = new Dictionary<Guid, Dictionary<Guid, Thread>>();
        private static Dictionary<Guid, Dictionary<Guid, Post>> postObjects = new Dictionary<Guid, Dictionary<Guid, Post>>();

        #endregion

        #region Static Methods

        #region Forum Methods
        /// <summary>
        /// Adds the forum.
        /// </summary>
        /// <param name="id">The id.</param>
        public static Forum AddForum(Guid id)
        {
            Forum forum = null;
            using (var db = AppDataModel.Instance.ReadOnlyDataContext)
            {
                forum = db.Forums.SingleOrDefault(f => f.Id == id);
            }

            if (forum == null)
            {
                throw new ArgumentException("id of forum does not exist in database");
            }
            else
            {
                return AddForum(forum);
            }
        }

        /// <summary>
        /// Adds the forum.
        /// </summary>
        /// <param name="forum">The forum.</param>
        public static Forum AddForum(Forum forum)
        {
            if (forum == null) throw new ArgumentNullException("forum", "forum must not be null");
            if (forum.ForumPropertySet == null) throw new ArgumentNullException("forum", "forum.ForumPropertySet must not be null");

            Forum current = forumObjects.SingleOrDefault(f => f.Id == forum.Id);
            if (current == null)
            {
                lock (forumObjects)
                {
                    current = forumObjects.SingleOrDefault(f => f.Id == forum.Id);
                    if (current == null)
                    {
                        forumObjects.Add(forum);
                        current = forum;
                    }
                }
                lock (postObjects)
                {
                    postObjects.Add(forum.Id, new Dictionary<Guid, Post>());
                }
                lock (threadObjects)
                {
                    threadObjects.Add(forum.Id, new Dictionary<Guid, Thread>());
                }
            }


            return current;
        }

        /// <summary>
        /// Updates the forum.
        /// </summary>
        /// <param name="forumId">The forum id.</param>
        public static void UpdateForum(Guid forumId)
        {
            Forum currentForum = forumObjects.SingleOrDefault(f => f.Id == forumId);
            if (currentForum != null)
            {
                Forum updatedForum = null;
                using (var db = AppDataModel.Instance.ReadOnlyDataContext)
                {
                    updatedForum = db.Forums.Single(f => f.Id == forumId);
                }
                Copy(updatedForum, ref currentForum);
            }
        }

        /// <summary>
        /// Gets the forum.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public static Forum GetForum(Guid id)
        {
            Forum forum = forumObjects.SingleOrDefault(f => f.Id == id);
            if (forum == null)
            {
                forum = AddForum(id);
            }

            return forum;
        }


        /// <summary>
        /// Gets the forum.
        /// </summary>
        /// <param name="forum">The forum.</param>
        /// <returns></returns>
        public static Forum GetForum(Forum forum)
        {
            Forum storedForum = forumObjects.SingleOrDefault(f => f.Id == forum.Id);
            if (storedForum == null)
            {
                storedForum = AddForum(forum);
            }

            return storedForum;
        }
        #endregion

        #region Post Methods
        /// <summary>
        /// Adds the post.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public static Post AddPost(Guid id)
        {
            Post post = null;
            using (var db = AppDataModel.Instance.ReadOnlyDataContext)
            {
                post = db.Posts.SingleOrDefault(p => p.Id == id);
            }
            if (post == null)
            {
                return null;
            }
            else
            {
                return AddPost(post);
            }
        }

        /// <summary>
        /// Adds the post.
        /// </summary>
        /// <param name="post">The post.</param>
        /// <returns>The added post</returns>
        public static Post AddPost(Post post)
        {
            if (post == null) throw new ArgumentNullException("post", "post must not be null");
            if (post.PostPropertySet == null) throw new ArgumentNullException("post", "post.PostPropertySet must not be null");

            Post current = null;
            postObjects[post.ForumId].TryGetValue(post.Id, out current);

            if (current == null)
            {
                lock (postObjects)
                {
                    postObjects[post.ForumId].TryGetValue(post.Id, out current);

                    if (current == null)
                    {
                        postObjects[post.ForumId].Add(post.Id, post);
                        current = post;
                    }

                }
            }

            return current;
        }


        /// <summary>
        /// Updates the post.
        /// </summary>
        /// <param name="forumId">The forum id.</param>
        /// <param name="postId">The post id.</param>
        public static void UpdatePost(Guid forumId, Guid postId)
        {
            Post currentPost = null;
            postObjects[forumId].TryGetValue(postId, out currentPost);
            if (currentPost != null)
            {
                Post updatedPost = null;
                using (var db = AppDataModel.Instance.ReadOnlyDataContext)
                {
                    updatedPost = db.Posts.Single(p => p.Id == postId);
                }
                Copy(updatedPost, ref currentPost);
            }
        }

        /// <summary>
        /// Gets the post.
        /// </summary>
        /// <param name="forumId">The forum id.</param>
        /// <param name="postId">The post id.</param>
        /// <returns></returns>
        public static Post GetPost(Guid forumId, Guid postId)
        {
            Post post = null;
            postObjects[forumId].TryGetValue(postId, out post);
            if (post == null)
            {
                post = AddPost(postId);
            }

            return post;
        }

        /// <summary>
        /// Gets the post.
        /// </summary>
        /// <param name="post">The post.</param>
        /// <returns></returns>
        public static Post GetPost(Post post)
        {
            Post storedPost = null;
            postObjects[post.ForumId].TryGetValue(post.Id, out storedPost);
            if (storedPost == null)
            {
                storedPost = AddPost(post);
            }

            return storedPost;
        }

        #endregion

        #region Thread Methods

        /// <summary>
        /// Adds the thread.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public static Thread AddThread(Guid id)
        {
            Thread thread = null;
            using (var db = AppDataModel.Instance.ReadOnlyDataContext)
            {
                thread = db.Threads.SingleOrDefault(t => t.Id == id);
            }
            if (thread == null)
            {
                throw new ArgumentException("id of thread does not exist in database");
            }
            else
            {
                return AddThread(thread);
            }
        }

        /// <summary>
        /// Adds the post.
        /// </summary>
        /// <param name="post">The post.</param>
        /// <returns>The added post</returns>
        public static Thread AddThread(Thread thread)
        {
            if (thread == null) throw new ArgumentNullException("thread", "thread must not be null");
            if (thread.ThreadPropertySet == null) throw new ArgumentNullException("thread", "thread.ThreadPropertySet must not be null");

            Thread current = null;
            threadObjects[thread.ForumId].TryGetValue(thread.Id, out current);

            if (current == null)
            {
                lock (threadObjects)
                {
                    threadObjects[thread.ForumId].TryGetValue(thread.Id, out current);

                    if (current == null)
                    {
                        threadObjects[thread.ForumId].Add(thread.Id, thread);
                        current = thread;
                    }
                }
            }


            return current;
        }


        /// <summary>
        /// Updates the thread.
        /// </summary>
        /// <param name="forumId">The forum id.</param>
        /// <param name="threadId">The thread id.</param>
        public static void UpdateThread(Guid forumId, Guid threadId)
        {
            Thread currentThread = null;
            threadObjects[forumId].TryGetValue(threadId, out currentThread);

            if (currentThread != null)
            {
                Thread updatedThread = null;
                using (var db = AppDataModel.Instance.ReadOnlyDataContext)
                {
                    updatedThread = db.Threads.Single(t => t.Id == threadId);
                }
                Copy(updatedThread, ref currentThread);
            }
        }


        /// <summary>
        /// Gets the thread.
        /// </summary>
        /// <param name="forumId">The forum id.</param>
        /// <param name="threadId">The thread id.</param>
        /// <returns></returns>
        public static Thread GetThread(Guid forumId, Guid threadId)
        {
            Thread thread = null;
            threadObjects[forumId].TryGetValue(threadId, out thread);
            if (thread == null)
            {
                thread = AddThread(threadId);
            }

            return thread;
        }

        /// <summary>
        /// Gets the post.
        /// </summary>
        /// <param name="post">The post.</param>
        /// <returns></returns>
        public static Thread GetThread(Thread thread)
        {
            Thread storedThread = null;
            threadObjects[thread.ForumId].TryGetValue(thread.Id, out storedThread);
            if (storedThread == null)
            {
                storedThread = AddThread(thread);
            }

            return storedThread;
        }
        #endregion

        #endregion



        /// <summary>
        /// Copy one object to another for each matching property
        /// </summary>
        /// <typeparam name="T">Type we are copying</typeparam>
        /// <param name="sourceObject"></param>
        /// <param name="destObject"></param>
        private static void Copy<T>(T sourceObject, ref T destinationObject)
        {
            if (sourceObject == null || destinationObject == null)
                return;

            Type sourceType = sourceObject.GetType();
            Type targetType = destinationObject.GetType();

            foreach (PropertyInfo p in sourceType.GetProperties())
            {
                object[] attrs = p.GetCustomAttributes(typeof(NotInDatabaseAttribute), false);
                if (attrs != null && attrs.Length > 0)
                    continue;

                // only copy ceartin types
                if (p.PropertyType != typeof(string) &&
                   p.PropertyType != typeof(int) &&
                   p.PropertyType != typeof(double) &&
                   p.PropertyType != typeof(Guid) &&
                   p.PropertyType != typeof(DateTime))
                {
                    continue;
                }
                if (!p.CanWrite || !p.CanRead) continue;

                PropertyInfo targetObj = targetType.GetProperty(p.Name);
                if (targetObj == null)
                    continue;

                targetObj.SetValue(destinationObject, p.GetValue(sourceObject, null), null);
            }
        }

    }
}
